package org.zhadoop.stream.cql;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zhadoop.stream.cql.exception.CQLException;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;

import jline.console.ConsoleReader;

/**
 * CQL的client，客户端程序的入口 这里会启动一个命令行的客户端
 * <p/>
 * 支持多个参数，即可以直接读取文件，也可以接受控制台输入
 * 
 * @author zwd
 *
 */
public class CQLClient {

	private static final Logger LOG = LoggerFactory.getLogger(CQLClient.class);

	private static final String DEFAULT_CLI_TIP = "Streaming>";
	private static final String WAITING_INPUT_TIP = ">";

	private static final String PRINTSTREAM_CHARSET = "UTF-8";
	
	private static final String CQL_EXIT_CMD = "exit";

	private static final String TEMP_FILE_FOR_SUSE = ".inputrc";

	private static final int ERROR_EXIT_CODE = 1;

	private CQLSessionState ss;
	
	private Driver driver;

	/**
	 * 命令行客户端入口
	 * 
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {

		int result = CQLSessionState.STATE_OK;
		CQLClient client = new CQLClient();

		/**
		 * 初始化用户的session
		 */
		result = client.initSessionState();

		if (result != CQLSessionState.STATE_OK) {
			System.exit(result);
		}

		// 解析参数
		if (!client.parseArgs(args)) {
			System.exit(CQLSessionState.STATE_NORMAL_EXIT);
		}

		// 为了屏蔽SUSE系统的警告，并无多大作用
		client.createTempFile();

		result = client.executeDriver();
		System.exit(result);
	}

	/**
	 * 命令行客户端执行入口
	 * 
	 * @return
	 * @throws IOException
	 */
	protected int executeDriver() throws IOException {
		if (ss.getFileName() != null) {
			try {
				return processFile(ss.getFileName());
			} catch (CQLException e) {
				LOG.error("Failed to process File {}", ss.getFileName());
				this.ss.getErr().println(e.getMessage());
				this.ss.getErr().flush();
				System.exit(ERROR_EXIT_CODE);
			}
		}
		return readConconsole();
	}

	private int readConconsole() throws IOException {
		//设置windows下jline可以运行
		System.setProperty("jline.WindowsTerminal.directConsole", "false");
		ConsoleReader reader = new ConsoleReader();
		reader.setBellEnabled(false);
        //这样就可以支持'!='这样的输入了。
        reader.setExpandEvents(false);
        reader.setCopyPasteDetection(true);
        
		String line;
		int ret = 0;
		StringBuilder prefix = new StringBuilder();
		printHelp();

		String tip = DEFAULT_CLI_TIP;

		while ((line = reader.readLine(tip)) != null) {
			tip = WAITING_INPUT_TIP;
			if (!prefix.toString().equals("")) {
				prefix.append("\n");
			}

			if (line.trim().startsWith("--")) {
				continue;
			}

			if (line.trim().endsWith(";") && !line.trim().endsWith("\\;")) {
				line = prefix.toString() + line;
				ret = processLine(line);
				prefix.delete(0, prefix.length());
				tip = DEFAULT_CLI_TIP;
			}else{
				prefix.append(line);
				continue;
			}
		}
		return ret;
	}

	/**
	 * 
	 * 
	 * 处理每一行
	 * @param line
	 * @return
	 */
	private int processLine(String line) {
		int lastRet = 0;
		int ret = 0;
		StringBuilder command = new StringBuilder();
		for(String oneCmd : line.split(";")){
			if(StringUtils.isBlank(oneCmd.trim())){
				continue;
			}
			if(StringUtils.endsWith(oneCmd, "\\")){
				command.append(StringUtils.chop(oneCmd)).append(";");
				continue;
			}else{
				command.append(oneCmd);
			}
			
			if(StringUtils.isBlank(command.toString())){
				continue;
			}
			
			ret  = processCQL(command.toString(),true);
			lastRet = ret;
		}
		return lastRet;
	}

	/**
	 * 处理CQL语句
	 * @param cql  cql语句
	 * @param isConsole 是否控制台
	 * @return
	 */
	private int processCQL(String cql, boolean isConsole) {
		String cmdTrimmed = cql.trim();
		if(cmdTrimmed.equalsIgnoreCase(CQL_EXIT_CMD)){
			ss.close();
			System.exit(0);
		}
		
		if(driver == null){
			driver = new Driver();
		}
		
		try {
			driver.run(cql);
			printHeader();
			printResult();
		} catch (CQLException e) {
			e.printStackTrace();
		}
		
		return 0;
	}

	private void printResult() {
		CQLResult cqlResult = driver.getResult();
		if(cqlResult == null){
			return;
		}
		List<String[]> results = cqlResult.getResults();
		if(results == null || results.size() == 0){
			return;
		}
		
		for(String[] heads : results){
			if(heads == null || heads.length == 0){
				continue;
			}
			this.ss.getOut().println(formatOutput(cqlResult.getFormatter(), heads));
		}
		this.ss.getOut().flush();
	}

	private void printHeader() {
		CQLResult result = driver.getResult();
		if(result == null){
			return;
		}
		String[] heads = result.getHeads();
		if(heads.length == 0){
			return;
		}
		this.ss.getOut().println(formatOutput(result.getFormatter(), heads));
		this.ss.getOut().flush();
	}

	private String formatOutput(String formatter, Object[] output) {
		if(Strings.isNullOrEmpty(formatter)){
			return Joiner.on(" , ").join(output);
		}
		return String.format(formatter, output);
	}

	private void printHelp() {
		ss.getInfo().println(" End CQL with ';' and end client with 'exit;' ");
		ss.getInfo().flush();
	}

	private int processFile(String fileName) throws CQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	/**
	 * 在用户目录下创建临时文件.inputrc，用于屏蔽掉在suse环境下控制台 打印警告信息
	 */
	private void createTempFile() {
		String userHome = System.getProperty("user.home");
		File file = new File(userHome, TEMP_FILE_FOR_SUSE);
		if (!file.exists()) {
			try {
				if (file.createNewFile()) {
					file.deleteOnExit();
				} else {
					LOG.warn("Create temp file " + TEMP_FILE_FOR_SUSE + ", but it doesn't effect CQL client.");
				}
			} catch (IOException e) {
				LOG.warn("Failed to create temp file " + TEMP_FILE_FOR_SUSE);
			}
		}
	}

	/**
	 * 解析系统参数
	 * 
	 * @param args
	 * @return
	 */
	private boolean parseArgs(String[] args) {
		LOG.debug("start to parse cli args");
		OptionProcessor processor = new OptionProcessor(ss);
		return processor.parseAgr(args);
	}

	/**
	 * 初始化用户session
	 * 
	 * @return
	 */
	private int initSessionState() {
		LOG.debug("start to  initlize session state");
		ss = new CQLSessionState();
		try {
			ss.setIn(System.in);
			ss.setOut(new PrintStream(System.out, true, PRINTSTREAM_CHARSET));
			ss.setInfo(new PrintStream(System.err, true, PRINTSTREAM_CHARSET));
			ss.setErr(new PrintStream(System.err, true, PRINTSTREAM_CHARSET));
		} catch (UnsupportedEncodingException e) {
			return CQLConst.I_3;
		}
		return 0;
	}
}
